/*
Minims .Net Playstation 3 UPNP ContentDirectory
Copyright (C) 2008  Guillaume Plourde

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; version 2
of the License.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Xml;

namespace minims_lib
{
	public class CDS_ITEM
	{
		public string classname;
	}
	public class MediaItem : CDS_ITEM
	{
		public int id;
		public string title;
		public string creator;
		public string filename;
		public long size;
		public string protocol;
		public bool restricted;
		public bool transcode;
        public string type;

		public MediaItem(string _title, string _creator, string _filename, long _size, string _protocol, int _id, string _t)
		{
			this.title = _title;
			this.creator = _creator;
			this.filename = _filename;
			this.size = _size;
			this.id = _id;
			this.protocol = _protocol;
            this.type = _t;

			base.classname = "media";
		}
	}
	public class Container : CDS_ITEM
	{
		public List<CDS_ITEM> items;

		int id;
		int parentid;
		string cdsclass;
		string title;

		public Container(string _title, string _cdsclass, int _id, int _parentid)
		{

			this.title = _title;
			this.id = _id;
			this.parentid = _parentid;
			this.cdsclass = _cdsclass;

			items = new List<CDS_ITEM>();

			base.classname = "container";
		}

		public void AddContainer(string title, string cdsclass, int i)
		{
			Container container = new Container(title, cdsclass, i, this.id);
			items.Add(container);
		}

		public string GetTitle
		{
			get
			{
				return this.title;
			}
		}
		public int GetIndex
		{
			get
			{
				return this.id;
			}
		}
		public int GetParentIndex
		{
			get
			{
				return this.parentid;
			}
		}
		public int GetNumChilds
		{
			get
			{
				return this.items.Count;
			}
		}
	}
	public class ContentDirectory : Service
	{
		int id;
		private int upid;

		public Container root;
		public long size;

		private ConfigManager conf;

        int DLNA_ORG_FLAG_SENDER_PACED               = (1 << 31);
        int DLNA_ORG_FLAG_TIME_BASED_SEEK            = (1 << 30);
        int DLNA_ORG_FLAG_BYTE_BASED_SEEK            = (1 << 29);
        int DLNA_ORG_FLAG_PLAY_CONTAINER             = (1 << 28);
        int DLNA_ORG_FLAG_S0_INCREASE                = (1 << 27);
        int DLNA_ORG_FLAG_SN_INCREASE                = (1 << 26);
        int DLNA_ORG_FLAG_RTSP_PAUSE                 = (1 << 25);
        int DLNA_ORG_FLAG_STREAMING_TRANSFER_MODE    = (1 << 24);
        int DLNA_ORG_FLAG_INTERACTIVE_TRANSFERT_MODE = (1 << 23);
        int DLNA_ORG_FLAG_BACKGROUND_TRANSFERT_MODE  = (1 << 22);
        int DLNA_ORG_FLAG_CONNECTION_STALL           = (1 << 21);
        int DLNA_ORG_FLAG_DLNA_V15                   = (1 << 20);

		private void GetContainers(Container parent, string dir, short type)
		{
			parent.AddContainer(dir, "object.container", id++);

			// fill items
			Container container = (Container)parent.items[parent.items.Count - 1];

			String[] files = { };

			try
			{
				files = Directory.GetFiles(dir);
			}
			catch (Exception e)
			{
				//Console.WriteLine("The process failed: {0}", e.ToString());
			}

            switch (type)
            {
                case 0:
                    foreach (string f in files)
			        {
				        FileInfo fi = new FileInfo(f);
        				
				        MediaItem item;
						foreach (MediaType m in MinimsConfig._mediaTypes)
						{
							if (m.type != 0)
								continue;

							if (fi.Extension.Contains(m.name))
							{
								size += fi.Length;

								StringBuilder mediaString = new StringBuilder();

								mediaString.Append("http-get:*:");

								// Hack: Somehow the xbox only list video with the x-ms-wmv mime type.

								if (conf.User.XboxCompat)
									mediaString.Append("video/x-ms-wmv:");
								else
									mediaString.Append(m.mine + ":");


								foreach (object key in m.dnla_keys.Keys)
								{
									mediaString.Append(key.ToString() + "=" + m.dnla_keys[key].ToString() + ";");
								}

								item = new MediaItem(fi.Name, "unknown author", fi.FullName, 0, mediaString.ToString() , id, "object.item.videoItem");
								id++;
								container.items.Add(item);						
							}
						}
			        }
                    break;
                case 1:
					foreach (string f in files)
					{
						FileInfo fi = new FileInfo(f);

						MediaItem item;
						foreach (MediaType m in MinimsConfig._mediaTypes)
						{
							if (m.type != 1)
								continue;

							if (fi.Extension.Contains(m.name))
							{
								size += fi.Length;

								StringBuilder mediaString = new StringBuilder();

								mediaString.Append("http-get:*:");
								mediaString.Append(m.mine + ":");

								foreach (object key in m.dnla_keys.Keys)
								{
									mediaString.Append(key.ToString() + "=" + m.dnla_keys[key].ToString() + ";");
								}

								item = new MediaItem(fi.Name, "unknown author", fi.FullName, 0, mediaString.ToString(), id, "object.item.audioItem");
								id++;
								container.items.Add(item);
							}
						}
					}
                    break;
                case 2:
                    foreach (string f in files)
                    {
                        FileInfo fi = new FileInfo(f);

                        MediaItem item;

                        if (fi.Extension.ToLower() == ".jpg")
                        {
                            size += fi.Length;
                            item = new MediaItem(fi.Name, "unknown author", fi.FullName, 0, "http-get:*:image/jpeg:*", id, "object.item.imageItem");
                            id++;
                            container.items.Add(item);
                        }
                    }
                    break;
            }

			// Recurse to next directory
			try
			{
				foreach (string strDirName in Directory.GetDirectories(dir))
				{
					GetContainers(container, strDirName, type);
				}
			}
			catch (Exception e)
			{
				//Console.WriteLine("The process failed: {0}", e.ToString());
			}
		}
		public Container FindContainer(Container c, int n)
		{
			if (c.GetIndex == n)
				return c;

			foreach (CDS_ITEM cc in c.items)
			{
				if (cc.classname == "container")
				{
					Container container = (Container)cc;
					Container temp = FindContainer(container, n);
					if (temp != null)
						return temp;
				}
			}
			return null;
		}
		public void CollectFilesFolders(string path) // types 0: video, 1: music, 2: photo
		{
			if (conf.User.Video)
				GetContainers((Container)root.items[0], path, 0);
			if (conf.User.Audio)
				GetContainers((Container)root.items[1], path, 1);
			if (conf.User.Image)
				GetContainers((Container)root.items[2], path, 2);
		}

		public ContentDirectory(ConfigManager configmanager) : base ("ContentDirectory")
		{
			size = 0;
			conf = configmanager;
			Random random = new Random();
			upid = random.Next(0, 100);
		}

		public void Update(List<string> folders)
		{
			size = 0;
			id = 0;

			// Create Root
			root = new Container("Root", "object.container", 0, -1);
			id++;

			// Create Childs
			if (conf.User.Video)
				root.AddContainer("Video", "object.container", 15);
			if (conf.User.Audio)
				root.AddContainer("Music", "object.container", id++);
			if (conf.User.Image)
				root.AddContainer("Photo", "object.container", id++);

			foreach (string folder in folders)
			{
				CollectFilesFolders(folder);
			}
			upid++;
		}

		private String BuildHTTPMessage(string func, string schemas, string body)
		{
			StringBuilder sb = new StringBuilder();

			sb.Append("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
			sb.Append("<s:Envelope s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\" xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">");
			sb.Append("<s:Body>");
			sb.Append("<u:" + func + "Response xmlns:u=\"" + schemas + "\">");
			sb.Append(body);
			sb.Append("</u:" + func + "Response>");
			sb.Append("</s:Body>");
			sb.Append("</s:Envelope>");

			return sb.ToString();
		}
		public String GetSearchCapabilities(string schemas)
		{
			Console.WriteLine("CDS: GetSearchCapabilities...");
			String response = "<SearchCaps>dc:title,dc:creator,upnp:class,upnp:album,res@protocolInfo,res@size,res@bitrate</SearchCaps>";
			return BuildHTTPMessage("GetSearchCapabilities", schemas, response);
		}
		public String GetSortCapabilities(string schemas)
		{
			Console.WriteLine("CDS: GetSortCapabilities...");
			String response = "<SortCaps>dc:title,dc:creator,upnp:class,upnp:album</SortCaps>";
			return BuildHTTPMessage("GetSortCapabilities", schemas, response);
		}
		public String GetSystemUpdateID(string schemas)
		{
			Console.WriteLine("CDS: GetSystemUpdateID...");

			String response = "<Id>" + GenerateUpdateID() + "</Id>";
			return BuildHTTPMessage("GetSystemUpdateID", schemas, response);
		}
		public String Browse(int ObjectID, string BrowserFlag, string Filter, int StartIndex, int RequestedCount, string SortCriteria, string schemas)
		{
			int i, c;

			Container container = FindContainer(root, ObjectID);

			if (container == null)
				return "error";

			StringBuilder sb = new StringBuilder();
			sb.Append("<Result>");
			sb.Append("&lt;DIDL-Lite xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\"&gt;");

			if (BrowserFlag == "BrowseMetadata")
			{
				sb.Append("&lt;container id=\"" + container.GetIndex + "\" parentID=\"" + container.GetParentIndex + "\" searchable=\"0\" restricted=\"1\" childcount=\"" + container.GetNumChilds + "\"&gt;");
				sb.Append("&lt;dc:title&gt;" + container.GetTitle + "&lt;/dc:title&gt;");
				sb.Append("&lt;upnp:class&gt;object.container&lt;/upnp:class&gt;");
				sb.Append("&lt;/container&gt;");
				sb.Append("&lt;/DIDL-Lite&gt;");
				sb.Append("</Result>");
				sb.Append("<NumberReturned>1</NumberReturned>");
				sb.Append("<TotalMatches>1</TotalMatches>");
				sb.Append("<UpdateID>" + GenerateUpdateID() + "</UpdateID>");
			}
			else
			{
				c = 0;

				//for (i = StartIndex; i < (RequestedCount + StartIndex); i++)
				for (i = StartIndex; i < 100; i++)
				{
					if (i > container.items.Count - 1)
						continue;

					if (container.items[i].classname == "container")
					{
						Container cn = (Container)container.items[i];

						sb.Append("&lt;container id=\"" + cn.GetIndex + "\" parentID=\"" + cn.GetParentIndex + "\" searchable=\"0\" restricted=\"1\" childCount=\"" + cn.GetNumChilds + "\"&gt;");
						sb.Append("&lt;dc:title&gt;" + cn.GetTitle + "&lt;/dc:title&gt;");
						sb.Append("&lt;upnp:class&gt;object.container.storageFolder&lt;/upnp:class&gt;");
						//sb.Append("&lt;upnp:writeStatus&gt;UNKNOWN&lt;/upnp:writeStatus&gt;");
						sb.Append("&lt;/container&gt;");
						c++;
					}
					else
					{
						MediaItem media = (MediaItem)container.items[i];

						sb.Append("&lt;item id=\"" + media.id + "\" parentID=\"" + container.GetIndex + "\" restricted=\"false\"&gt;");
						sb.Append("&lt;dc:title&gt;" + media.title + "&lt;/dc:title&gt;");
						sb.Append("&lt;dc:creator&gt;" + media.creator + "&lt;/dc:creator&gt;");
						sb.Append("&lt;upnp:class&gt;" + media.type + "&lt;/upnp:class&gt;");
						sb.Append("&lt;res protocolInfo=\"" + media.protocol + "\"&gt;");
						sb.Append("http://" + conf.GetHostIp + ":" + conf.User.Port + "/Media/" + container.GetIndex + "/" + media.id);
						sb.Append("&lt;/res&gt;");
						sb.Append("&lt;/item&gt;");
						c++;
					}
				}

				sb.Append("&lt;/DIDL-Lite&gt;");
				sb.Append("</Result>");
				sb.Append("<NumberReturned>" + c + "</NumberReturned>");
				sb.Append("<TotalMatches>" + c + "</TotalMatches>");
				sb.Append("<UpdateID>" + GenerateUpdateID() + "</UpdateID>");
			}
			return BuildHTTPMessage("Browse", schemas, sb.ToString());
		}
		public string Search()
		{
			return "";
		}

		private int GenerateUpdateID()
		{
			return upid;
		}

		#region Service Description XML, use GetDescription()
		static public string GetDescription()
		{
			// Edit this xml to match device capabilities
			StringBuilder desc = new StringBuilder();
			desc.Append("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
			desc.Append("<scpd xmlns=\"urn:schemas-upnp-org:service-1-0\"><specVersion><major>1</major><minor>0</minor></specVersion>");
			desc.Append("<actionList>");
			desc.Append("<action><name>GetSearchCapabilities</name><argumentList><argument><name>SearchCaps</name><direction>out</direction><relatedStateVariable>SearchCapabilities</relatedStateVariable></argument></argumentList></action>");
			desc.Append("<action><name>GetSortCapabilities</name><argumentList><argument><name>SortCaps</name><direction>out</direction><relatedStateVariable>SortCapabilities</relatedStateVariable></argument></argumentList></action>");
			desc.Append("<action><name>GetSystemUpdateID</name><argumentList><argument><name>Id</name><direction>out</direction><relatedStateVariable>SystemUpdateID</relatedStateVariable></argument></argumentList></action>");
			desc.Append("<action><name>Browse</name><argumentList><argument><name>ObjectID</name><direction>in</direction><relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable></argument><argument><name>BrowseFlag</name><direction>in</direction><relatedStateVariable>A_ARG_TYPE_BrowseFlag</relatedStateVariable></argument><argument><name>Filter</name><direction>in</direction><relatedStateVariable>A_ARG_TYPE_Filter</relatedStateVariable></argument><argument><name>StartingIndex</name><direction>in</direction><relatedStateVariable>A_ARG_TYPE_Index</relatedStateVariable></argument><argument><name>RequestedCount</name><direction>in</direction><relatedStateVariable>A_ARG_TYPE_Count</relatedStateVariable></argument><argument><name>SortCriteria</name><direction>in</direction><relatedStateVariable>A_ARG_TYPE_SortCriteria</relatedStateVariable></argument><argument><name>Result</name><direction>out</direction><relatedStateVariable>A_ARG_TYPE_Result</relatedStateVariable></argument><argument><name>NumberReturned</name><direction>out</direction><relatedStateVariable>A_ARG_TYPE_Count</relatedStateVariable></argument><argument><name>TotalMatches</name><direction>out</direction><relatedStateVariable>A_ARG_TYPE_Count</relatedStateVariable></argument><argument><name>UpdateID</name><direction>out</direction><relatedStateVariable>A_ARG_TYPE_UpdateID</relatedStateVariable></argument></argumentList></action>");
			desc.Append("</actionList>");
			desc.Append("<serviceStateTable>");
			desc.Append("<stateVariable sendEvents=\"no\"><name>A_ARG_TYPE_ObjectID</name><dataType>string</dataType></stateVariable>");
			desc.Append("<stateVariable sendEvents=\"no\"><name>A_ARG_TYPE_Result</name><dataType>string</dataType></stateVariable>");
			desc.Append("<stateVariable sendEvents=\"no\"><name>A_ARG_TYPE_SearchCriteria</name><dataType>string</dataType></stateVariable>");
			desc.Append("<stateVariable sendEvents=\"no\"><name>A_ARG_TYPE_BrowseFlag</name><dataType>string</dataType><allowedValueList><allowedValue>BrowseMetadata</allowedValue><allowedValue>BrowseDirectChildren</allowedValue></allowedValueList></stateVariable>");
			desc.Append("<stateVariable sendEvents=\"no\"><name>A_ARG_TYPE_Filter</name><dataType>string</dataType></stateVariable>");
			desc.Append("<stateVariable sendEvents=\"no\"><name>A_ARG_TYPE_SortCriteria</name><dataType>string</dataType></stateVariable>");
			desc.Append("<stateVariable sendEvents=\"no\"><name>A_ARG_TYPE_Index</name><dataType>ui4</dataType></stateVariable>");
			desc.Append("<stateVariable sendEvents=\"no\"><name>A_ARG_TYPE_Count</name><dataType>ui4</dataType></stateVariable>");
			desc.Append("<stateVariable sendEvents=\"no\"><name>A_ARG_TYPE_UpdateID</name><dataType>ui4</dataType></stateVariable>");
			desc.Append("<stateVariable sendEvents=\"no\"><name>SearchCapabilities</name><dataType>string</dataType></stateVariable>");
			desc.Append("<stateVariable sendEvents=\"no\"><name>SortCapabilities</name><dataType>string</dataType></stateVariable>");
			desc.Append("<stateVariable sendEvents=\"yes\"><name>SystemUpdateID</name><dataType>ui4</dataType></stateVariable>");
			desc.Append("<stateVariable sendEvents=\"yes\"><name>ContainerUpdateIDs</name><dataType>string</dataType></stateVariable>");
			desc.Append("</serviceStateTable></scpd>");
			return desc.ToString();
		}
		#endregion
	}
}
